#include "8086emu.h"

static inline uint16_t emu_cpu_read_reg(emu_cpu_t *cpu, uint8_t reg_id, bool is_byte)
{
    if (is_byte) {
        switch (reg_id) {
            case REG_AL:
                return cpu->regs[REG_AX] & 0xff;
            case REG_AH:
                return (cpu->regs[REG_AX] >> 8) & 0xff;
            case REG_BL:
                return cpu->regs[REG_BX] & 0xff;
            case REG_BH:
                return (cpu->regs[REG_BX] >> 8) & 0xff;
            case REG_CL:
                return cpu->regs[REG_CX] & 0xff;
            case REG_CH:
                return (cpu->regs[REG_CX] >> 8) & 0xff;
            case REG_DL:
                return cpu->regs[REG_DX] & 0xff;
            case REG_DH:
                return (cpu->regs[REG_DX] >> 8) & 0xff;
        }
    } else return cpu->regs[reg_id];
}

#define emu_cpu_read_word_reg(cpu, reg_id) emu_cpu_read_reg(cpu, reg_id, false)
#define emu_cpu_read_byte_reg(cpu, reg_id) (emu_cpu_read_reg(cpu, reg_id, true) & 0xff)

static inline void emu_cpu_write_reg(emu_cpu_t *cpu, uint8_t reg_id, uint16_t data, bool is_byte)
{
    if (is_byte) {
        switch (reg_id) {
            case REG_AL:
                cpu->regs[REG_AX] &= 0xff00;
                cpu->regs[REG_AX] |= (data & 0xff);
                break;
            case REG_AH:
                cpu->regs[REG_AX] &= 0x00ff;
                cpu->regs[REG_AX] |= ((data & 0xff) << 8) & 0xff00;
                break;
            case REG_BL:
                cpu->regs[REG_BX] &= 0xff00;
                cpu->regs[REG_BX] |= (data & 0xff);
                break;
            case REG_BH:
                cpu->regs[REG_BX] &= 0x00ff;
                cpu->regs[REG_BX] |= ((data & 0xff) << 8) & 0xff00;
                break;
            case REG_CL:
                cpu->regs[REG_CX] &= 0xff00;
                cpu->regs[REG_CX] |= (data & 0xff);
                break;
            case REG_CH:
                cpu->regs[REG_CX] &= 0x00ff;
                cpu->regs[REG_CX] |= ((data & 0xff) << 8) & 0xff00;
                break;
            case REG_DL:
                cpu->regs[REG_DX] &= 0xff00;
                cpu->regs[REG_DX] |= (data & 0xff);
                break;
            case REG_DH:
                cpu->regs[REG_DX] &= 0x00ff;
                cpu->regs[REG_DX] |= ((data & 0xff) << 8) & 0xff00;
                break;
        }
    } else cpu->regs[reg_id] = data;
}

#define emu_cpu_write_word_reg(cpu, reg_id, data) emu_cpu_write_reg(cpu, reg_id, data, false)
#define emu_cpu_write_byte_reg(cpu, reg_id, data) emu_cpu_write_reg(cpu, reg_id, 0x00FF & data, true)

static inline uint16_t emu_cpu_read_mem(emu_cpu_t *cpu, uint8_t seg_id, uint16_t address, bool is_byte)
{
    uint16_t seg_base = cpu->segs[seg_id] * 16;
    uint32_t real_address = (uint32_t) seg_base + address;
    if (real_address >= 0x100000) real_address -= 0x100000;
    uint8_t lo = cpu->memory[real_address], hi = cpu->memory[real_address + 1];
    if (is_byte) return lo;
    return lo | (hi << 8);
}

#define emu_cpu_read_byte_mem(cpu, seg_id, address) emu_cpu_read_mem(cpu, seg_id, address, true)
#define emu_cpu_read_word_mem(cpu, seg_id, address) emu_cpu_read_mem(cpu, seg_id, address, false)

static inline void emu_cpu_write_mem(emu_cpu_t *cpu, uint8_t seg_id, uint16_t address, uint16_t data, bool is_byte)
{
    uint16_t seg_base = cpu->segs[seg_id] * 16;
    uint32_t real_address = (uint32_t) seg_base + address;
    if (real_address >= 0x100000) real_address -= 0x100000;
    uint8_t lo = data & 0xff, hi = data >> 8;
    cpu->memory[real_address] = lo;
    if (!is_byte) cpu->memory[real_address + 1] = hi;
}

#define emu_cpu_write_byte_mem(cpu, seg_id, address, data) emu_cpu_write_mem(cpu, seg_id, address, data, true)
#define emu_cpu_write_word_mem(cpu, seg_id, address, data) emu_cpu_write_mem(cpu, seg_id, address, data, false)

static inline void emu_cpu_push_to_stack(emu_cpu_t *cpu, uint16_t data)
{
    emu_cpu_write_word_reg(cpu, REG_SP, emu_cpu_read_word_reg(cpu, REG_SP) - 2);
    uint16_t stack = emu_cpu_read_word_reg(cpu, REG_SP);
    emu_cpu_write_word_mem(cpu, SEG_SS, stack, data);
}

static inline uint16_t emu_cpu_pop_from_stack(emu_cpu_t *cpu)
{
    uint16_t content = emu_cpu_read_word_mem(cpu, SEG_SS, emu_cpu_read_word_reg(cpu, REG_SP));
    emu_cpu_write_word_reg(cpu, REG_SP, emu_cpu_read_word_reg(cpu, REG_SP) + 2);
    return content;
}

#define emu_cpu_mask_flag(cpu, flag_id) (cpu->flags.bits.flag_id = 1)
#define emu_cpu_clear_flag(cpu, flag_id) (cpu->flags.bits.flag_id = 0)
#define emu_cpu_read_flag(cpu, flag_id) cpu->flags.bits.flag_id

static inline void emu_spurious_port_handler(uint16_t port, uint16_t *data, uint8_t data_bytes, bool input)
{
    if (input) {
        printf("%s", "in");
        if (data_bytes == 1) putchar('b');
        else if (data_bytes == 2) putchar('w');
        else putchar('?');
        printf(" %d executed\n", port);
        *data = 0xdead;
    } else {
        printf("%s", "out");
        if (data_bytes == 1) putchar('b');
        else if (data_bytes == 2) putchar('w');
        else putchar('?');
        printf(" %d %d executed\n", port, *data);
    }
}

static inline void emu_spurious_intr_handler(uint8_t port)
{
    if (port >= 0x00 && port <= 0x1f) {
        printf("exception %d occured\n", port);
    }
    else printf("int %d executed\n", port);
}

inline void emu_physical_intr_handler(uint8_t port)
{
    // TODO
}

void emu_cpu_init(emu_cpu_t *cpu, uint32_t mem_size, uint16_t base_ip)
{
    memset(cpu->regs, 0, sizeof(cpu->regs));
    memset(cpu->segs, 0, sizeof(cpu->segs));
    cpu->flags.data = 0;
    if (base_ip != (uint16_t) -1) cpu->memory = NULL;
    else cpu->memory = (uint8_t *) calloc(mem_size, sizeof(uint8_t));
    cpu->ip = 0;
    cpu->regs[REG_SP] = mem_size - 1;
    for (int i = 0; i < 255; i++) {
        cpu->ports[i] = emu_spurious_port_handler;
        cpu->intrs[i] = emu_spurious_intr_handler;
    }
}

void emu_cpu_dump(emu_cpu_t *cpu)
{
    char *regs[] = {"AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI"};
    for (int i = 0; i < 4; i++) {
        printf("%s=%04X ", regs[i], cpu->regs[i]);
    }
    putchar('\n');
    for (int i = 4; i < 8; i++) {
        printf("%s=%04X ", regs[i], cpu->regs[i]);
    }
    putchar('\n');
    printf("DS=%04X ES=%04X SS=%04X CS:IP=%04X:%04X \n", cpu->segs[SEG_DS], cpu->segs[SEG_ES], cpu->segs[SEG_SS], cpu->segs[SEG_CS], cpu->ip);
    printf("FLAGS: ");
    if (cpu->flags.bits.CF) printf("CF "); else printf("cf ");
    if (cpu->flags.bits.PF) printf("PF "); else printf("pf ");
    if (cpu->flags.bits.AF) printf("AF "); else printf("af ");
    if (cpu->flags.bits.ZF) printf("ZF "); else printf("zf ");
    if (cpu->flags.bits.SF) printf("SF "); else printf("sf ");
    if (cpu->flags.bits.TF) printf("TF "); else printf("tf ");
    if (cpu->flags.bits.IF) printf("IF "); else printf("if ");
    if (cpu->flags.bits.DF) printf("DF "); else printf("df ");
    if (cpu->flags.bits.OF) printf("OF "); else printf("of ");
}

static inline void emu_cpu_mov_imm_to_seg(emu_cpu_t *cpu, int mov_to_seg, bool operand_is_word)
{
#define NEXT_BYTE() emu_cpu_read_byte_mem(cpu, SEG_CS, cpu->ip++)
#define NEXT_SHORT() ({ \
    uint8_t lo = NEXT_BYTE(); \
    uint8_t hi = NEXT_BYTE(); \
    (hi << 8) | lo;\
})
    uint8_t towhich = NEXT_BYTE();
    int total_offset = 0;
    if (towhich == 6) {
        total_offset = NEXT_SHORT();
    } else {
        if (towhich & 0x40) total_offset = NEXT_BYTE(), towhich &= ~0x40;
        else if (towhich & 0x80) total_offset = NEXT_SHORT(), towhich &= ~0x80;
        // craft total addr
        switch (towhich) {
            case 0:
                total_offset += emu_cpu_read_word_reg(cpu, REG_BX);
                total_offset += emu_cpu_read_word_reg(cpu, REG_SI);
                break;
            case 1:
                total_offset += emu_cpu_read_word_reg(cpu, REG_BX);
                total_offset += emu_cpu_read_word_reg(cpu, REG_DI);
                break;
            case 2:
                total_offset += emu_cpu_read_word_reg(cpu, REG_BP);
                total_offset += emu_cpu_read_word_reg(cpu, REG_SI);
                break;
            case 3:
                total_offset += emu_cpu_read_word_reg(cpu, REG_BP);
                total_offset += emu_cpu_read_word_reg(cpu, REG_DI);
                break;
            case 4:
                total_offset += emu_cpu_read_word_reg(cpu, REG_SI);
                break;
            case 5:
                total_offset += emu_cpu_read_word_reg(cpu, REG_DI);
                break;
            case 6:
                total_offset += emu_cpu_read_word_reg(cpu, REG_BP);
                break;
            case 7:
                total_offset += emu_cpu_read_word_reg(cpu, REG_BX);
                break;
        }
    }
    uint16_t operand = 0;
    if (operand_is_word) operand = NEXT_SHORT();
    else operand = NEXT_BYTE();
    emu_cpu_write_mem(cpu, mov_to_seg, total_offset, operand, !operand_is_word);
#undef NEXT_SHORT
#undef NEXT_BYTE
}

int popcnt(uint16_t u)
{
    int ret = 0;
    while (u) {
        u &= u - 1;
        ret++;
    }
    return ret;
}

static inline void emu_cpu_affect_flags_internal(emu_cpu_t *cpu, uint16_t opa, uint16_t opb, uint32_t op_result, bool is_byte)
{
    if (is_byte) {
        uint8_t result = op_result & 0xff;
        uint8_t op1 = opa & 0xff;
        uint8_t op2 = opb & 0xff;
        int result_ones = popcnt(result);
        cpu->flags.bits.PF = result_ones % 2 == 0;
        cpu->flags.bits.CF = op1 == opa && op2 == opb && (op_result & 0x100);
        cpu->flags.bits.ZF = result == 0;
        cpu->flags.bits.SF = op_result & 0x80;
        cpu->flags.bits.AF = (opa & 0xf) + (opb & 0xf) >= 0x10;
        int8_t signed_op1 = (int8_t) op1;
        int8_t signed_op2 = (int8_t) op2;
        cpu->flags.bits.OF = (op1 > 0 && op2 > 0 && op1 + op2 < 0) || (op1 < 0 && op2 < 0 && op1 + op2 > 0);
    } else {
        uint32_t result = op_result;
        uint16_t op1 = opa;
        uint16_t op2 = opb;
        int result_ones = popcnt(result);
        cpu->flags.bits.PF = result_ones % 2 == 0;
        cpu->flags.bits.CF = op1 == opa && op2 == opb && (op_result & 0x10000);
        cpu->flags.bits.ZF = op_result == 0;
        cpu->flags.bits.SF = op_result & 0x8000;
        cpu->flags.bits.AF = (opa & 0xff) + (opb & 0xff) >= 0x100;
        int16_t signed_op1 = (int16_t) op1;
        int16_t signed_op2 = (int16_t) op2;
        cpu->flags.bits.OF = (op1 > 0 && op2 > 0 && op1 + op2 < 0) || (op1 < 0 && op2 < 0 && op1 + op2 > 0);
    }
}

#define emu_cpu_affect_flags_byte(cpu, opa, op, opb) emu_cpu_affect_flags_internal(cpu, opa, opb, (uint32_t) opa op opb, true)
#define emu_cpu_affect_flags_word(cpu, opa, op, opb) emu_cpu_affect_flags_internal(cpu, opa, opb, (uint32_t) opa op opb, false)

static inline void emu_cpu_do_interrupt(emu_cpu_t *cpu, int port)
{
    emu_cpu_push_to_stack(cpu, cpu->flags.data);
    emu_cpu_clear_flag(cpu, IF);
    emu_cpu_clear_flag(cpu, TF);
    emu_cpu_push_to_stack(cpu, cpu->segs[SEG_CS]);
    emu_cpu_push_to_stack(cpu, cpu->ip);
    cpu->intrs[port](port);
    return;
}

void emu_cpu_execute(emu_cpu_t *cpu)
{
#define NEXT_BYTE() emu_cpu_read_byte_mem(cpu, SEG_CS, cpu->ip++)
#define NEXT_SHORT() ({ \
    uint8_t lo = NEXT_BYTE(); \
    uint8_t hi = NEXT_BYTE(); \
    (hi << 8) | lo;\
})
    while (true) {
        uint8_t instruction;
        switch (instruction = NEXT_BYTE()) {
            case OP_MOV_TO_MEM_SEG_ES:
            case OP_MOV_TO_MEM_SEG_SS:
            case OP_MOV_TO_MEM_SEG_CS:
            case OP_MOV_TO_MEM_SEG_DS: {
                int mov_to_seg;
                if (instruction == OP_MOV_TO_MEM_SEG_CS) mov_to_seg = SEG_CS;
                else if (instruction == OP_MOV_TO_MEM_SEG_ES) mov_to_seg = SEG_ES;
                else if (instruction == OP_MOV_TO_MEM_SEG_SS) mov_to_seg = SEG_SS;
                else mov_to_seg = SEG_DS;
                int operand_size = NEXT_BYTE();
                if (operand_size == OP_MOV_BYTE_IMM_TO_MEM_SEG_DS || operand_size == OP_MOV_WORD_IMM_TO_MEM_SEG_DS) {
                    emu_cpu_mov_imm_to_seg(cpu, mov_to_seg, operand_size - OP_MOV_BYTE_IMM_TO_MEM_SEG_DS);
                }
                break;
            }
            case OP_MOV_BYTE_IMM_TO_MEM_SEG_DS:
            case OP_MOV_WORD_IMM_TO_MEM_SEG_DS: {
                emu_cpu_mov_imm_to_seg(cpu, SEG_DS, instruction - OP_MOV_BYTE_IMM_TO_MEM_SEG_DS);
                break;
            }
            case OP_INC_AX ... OP_INC_DI: {
                uint8_t towhich = instruction - OP_INC_AX;
                emu_cpu_affect_flags_word(cpu, emu_cpu_read_word_reg(cpu, towhich), +, 1);
                emu_cpu_write_word_reg(cpu, towhich, emu_cpu_read_word_reg(cpu, towhich) + 1);
                break;
            }
            case OP_DEC_AX ... OP_DEC_DI: {
                uint8_t towhich = instruction - OP_DEC_AX;
                emu_cpu_affect_flags_word(cpu, emu_cpu_read_word_reg(cpu, towhich), -, 1);
                emu_cpu_write_word_reg(cpu, towhich, emu_cpu_read_word_reg(cpu, towhich) - 1);
                break;
            }
            case OP_PUSH_AX ... OP_PUSH_DI: {
                uint8_t towhich = instruction - OP_PUSH_AX;
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, towhich));
                break;
            }
            case OP_POP_AX ... OP_POP_DI: {
                uint8_t towhich = instruction - OP_POP_AX;
                emu_cpu_write_word_reg(cpu, towhich, emu_cpu_pop_from_stack(cpu));
                break;
            }
            case OP_PUSHAW: {
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, REG_AX));
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, REG_CX));
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, REG_DX));
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, REG_BX));
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, REG_SP));
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, REG_BP));
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, REG_SI));
                emu_cpu_push_to_stack(cpu, emu_cpu_read_word_reg(cpu, REG_DI));
                break;
            }
            case OP_POPAW: {
                emu_cpu_write_word_reg(cpu, REG_DI, emu_cpu_pop_from_stack(cpu));
                emu_cpu_write_word_reg(cpu, REG_SI, emu_cpu_pop_from_stack(cpu));
                emu_cpu_write_word_reg(cpu, REG_BP, emu_cpu_pop_from_stack(cpu));
                (void) emu_cpu_pop_from_stack(cpu);
                emu_cpu_write_word_reg(cpu, REG_BX, emu_cpu_pop_from_stack(cpu));
                emu_cpu_write_word_reg(cpu, REG_DX, emu_cpu_pop_from_stack(cpu));
                emu_cpu_write_word_reg(cpu, REG_CX, emu_cpu_pop_from_stack(cpu));
                emu_cpu_write_word_reg(cpu, REG_AX, emu_cpu_pop_from_stack(cpu));
                break;
            }
            case OP_PUSH_WR_IMM: {
                emu_cpu_push_to_stack(cpu, NEXT_SHORT());
                break;
            }
            case OP_PUSH_BT_IMM: {
                emu_cpu_push_to_stack(cpu, NEXT_BYTE());
                break;
            }
            case OP_JO: {
                uint8_t offset = NEXT_BYTE();
                if (emu_cpu_read_flag(cpu, OF)) cpu->ip += offset;
                break;
            }
            case OP_JNO: {
                uint8_t offset = NEXT_BYTE();
                if (!emu_cpu_read_flag(cpu, OF)) cpu->ip += offset;
                break;
            }
            case OP_JC: {
                uint8_t offset = NEXT_BYTE();
                if (emu_cpu_read_flag(cpu, CF)) cpu->ip += offset;
                break;
            }
            case OP_JNC: {
                uint8_t offset = NEXT_BYTE();
                if (!emu_cpu_read_flag(cpu, CF)) cpu->ip += offset;
                break;
            }
            case OP_JZ: {
                uint8_t offset = NEXT_BYTE();
                if (emu_cpu_read_flag(cpu, ZF)) cpu->ip += offset;
                break;
            }
            case OP_JNZ: {
                uint8_t offset = NEXT_BYTE();
                if (!emu_cpu_read_flag(cpu, ZF)) cpu->ip += offset;
                break;
            }
            case OP_JNA: {
                uint8_t offset = NEXT_BYTE();
                if (emu_cpu_read_flag(cpu, CF) || emu_cpu_read_flag(cpu, ZF)) cpu->ip += offset;
                break;
            }
            case OP_JA: {
                uint8_t offset = NEXT_BYTE();
                if (!emu_cpu_read_flag(cpu, CF) && !emu_cpu_read_flag(cpu, ZF)) cpu->ip += offset;
                break;
            }
            case OP_JS: {
                uint8_t offset = NEXT_BYTE();
                if (emu_cpu_read_flag(cpu, SF)) cpu->ip += offset;
                break;
            }
            case OP_JNS: {
                uint8_t offset = NEXT_BYTE();
                if (!emu_cpu_read_flag(cpu, SF)) cpu->ip += offset;
                break;
            }
            case OP_JPE: {
                uint8_t offset = NEXT_BYTE();
                if (emu_cpu_read_flag(cpu, PF)) cpu->ip += offset;
                break;
            }
            case OP_JPO: {
                uint8_t offset = NEXT_BYTE();
                if (!emu_cpu_read_flag(cpu, PF)) cpu->ip += offset;
                break;
            }
            case OP_JL: {
                uint8_t offset = NEXT_BYTE();
                if (emu_cpu_read_flag(cpu, OF) ^ emu_cpu_read_flag(cpu, SF)) cpu->ip += offset;
                break;
            }
            case OP_JNL: {
                uint8_t offset = NEXT_BYTE();
                if (!(emu_cpu_read_flag(cpu, OF) ^ emu_cpu_read_flag(cpu, SF))) cpu->ip += offset;
                break;
            }
            case OP_JNG: {
                uint8_t offset = NEXT_BYTE();
                if ((emu_cpu_read_flag(cpu, OF) ^ emu_cpu_read_flag(cpu, SF)) || emu_cpu_read_flag(cpu, ZF)) cpu->ip += offset;
                break;
            }
            case OP_JG: {
                uint8_t offset = NEXT_BYTE();
                if (!(emu_cpu_read_flag(cpu, OF) ^ emu_cpu_read_flag(cpu, SF)) && !emu_cpu_read_flag(cpu, ZF)) cpu->ip += offset;
                break;
            }
            case OP_NOP: {
                break;
            }
            case OP_CBW: {
                uint8_t al = emu_cpu_read_byte_reg(cpu, REG_AL);
                if (al >> 7) emu_cpu_write_byte_reg(cpu, REG_AH, 0xff);
                else emu_cpu_write_byte_reg(cpu, REG_AH, 0x00);
                break;
            }
            case OP_CWD: {
                uint16_t ax = emu_cpu_read_word_reg(cpu, REG_AX);
                if (ax >> 15) emu_cpu_write_word_reg(cpu, REG_DX, 0xffff);
                else emu_cpu_write_word_reg(cpu, REG_DX, 0x00);
                break;
            }
            case OP_PUSHFW: {
                emu_cpu_push_to_stack(cpu, cpu->flags.data);
                break;
            }
            case OP_POPFW: {
                cpu->flags.data = emu_cpu_pop_from_stack(cpu);
                break;
            }
            case OP_SAHF: {
                uint8_t bits = emu_cpu_read_byte_reg(cpu, REG_AH);
                cpu->flags.data = (cpu->flags.data & 0xff00) | bits;
                break;
            }
            case OP_LAHF: {
                uint8_t bits = cpu->flags.data & 0xff;
                emu_cpu_write_byte_reg(cpu, REG_AH, bits);
                break;
            }
            case OP_MOV_BT_IMM_TO_AL ... OP_MOV_BT_IMM_TO_BH: {
                uint8_t towhich = instruction - OP_MOV_BT_IMM_TO_AL;
                uint8_t imm = NEXT_BYTE();
                emu_cpu_write_byte_reg(cpu, towhich, imm);
                break;
            }
            case OP_MOV_WR_IMM_TO_AX ... OP_MOV_WR_IMM_TO_DI: {
                uint8_t towhich = instruction - OP_MOV_WR_IMM_TO_AX;
                uint16_t imm = NEXT_SHORT();
                emu_cpu_write_word_reg(cpu, towhich, imm);
                break;
            }
            case OP_MOV_BT_REG_TO_REG: {
                uint8_t whichto = NEXT_BYTE();
                if (whichto < 0xc0) {
                    printf("Unknown operator for operation MOV_BT_REG_TO_REG %d", whichto);
                    return;
                }
                uint8_t whichregto = (whichto - 0xc0) / 8;
                uint8_t towhich = (whichto - 0xc0) % 8;
                emu_cpu_write_byte_reg(cpu, towhich, emu_cpu_read_byte_reg(cpu, whichregto));
                break;
            }
            case OP_MOV_WR_REG_TO_REG: {
                uint8_t whichto = NEXT_BYTE();
                if (whichto < 0xc0) {
                    printf("Unknown operator for operation MOV_WR_REG_TO_REG %d", whichto);
                    return;
                }
                uint8_t whichregto = (whichto - 0xc0) / 8;
                uint8_t towhich = (whichto - 0xc0) % 8;
                emu_cpu_write_word_reg(cpu, towhich, emu_cpu_read_word_reg(cpu, whichregto));
                break;
            }
            case OP_MOV_SEG_TO_REG: {
                uint8_t whichto = NEXT_BYTE();
                uint8_t towhich = (whichto - 0xc0) % 8;
                whichto = (whichto - 0xc0) / 8;
                emu_cpu_write_word_reg(cpu, towhich, cpu->segs[whichto]);
                break;
            }
            case OP_MOV_REG_TO_SEG: {
                uint8_t whichto = NEXT_BYTE();
                uint8_t towhich = (whichto - 0xc0) / 8;
                whichto = (whichto - 0xc0) % 8;
                if (towhich != SEG_CS) cpu->segs[towhich] = emu_cpu_read_word_reg(cpu, whichto);
                else {
                    emu_cpu_do_interrupt(cpu, 6);
                }
                break;
            }
            case OP_RET: {
                cpu->ip = emu_cpu_pop_from_stack(cpu);
                break;
            }
            case OP_LEAVE: {
                emu_cpu_write_word_reg(cpu, REG_SP, emu_cpu_read_word_reg(cpu, REG_BP));
                emu_cpu_write_word_reg(cpu, REG_BP, emu_cpu_pop_from_stack(cpu));
                break;
            }
            case OP_INT3: {
                emu_cpu_do_interrupt(cpu, 3);
                break;
            }
            case OP_INT: {
                emu_cpu_do_interrupt(cpu, NEXT_BYTE());
                break;
            }
            case OP_IRET: {
                cpu->ip = emu_cpu_pop_from_stack(cpu);
                cpu->segs[SEG_CS] = emu_cpu_pop_from_stack(cpu);
                cpu->flags.data = emu_cpu_pop_from_stack(cpu);
                break;
            }
            case OP_IN_ALIMM: {
                uint16_t port = NEXT_BYTE();
                uint16_t data = 0;
                cpu->ports[port](port, &data, 1, true);
                emu_cpu_write_byte_reg(cpu, REG_AL, data);
                break;
            }
            case OP_IN_AXIMM: {
                uint16_t port = NEXT_BYTE();
                uint16_t data = 0;
                cpu->ports[port](port, &data, 2, true);
                emu_cpu_write_word_reg(cpu, REG_AX, data);
                break;
            }
            case OP_OUT_IMMAL: {
                uint16_t port = NEXT_BYTE();
                uint16_t data = emu_cpu_read_byte_reg(cpu, REG_AL);
                cpu->ports[port](port, &data, 1, false);
                break;
            }
            case OP_OUT_IMMAX: {
                uint16_t port = NEXT_BYTE();
                uint16_t data = emu_cpu_read_word_reg(cpu, REG_AL);
                cpu->ports[port](port, &data, 2, false);
                break;
            }
            case OP_CALL_NEAR_IMM: {
                uint16_t offset = NEXT_SHORT();
                emu_cpu_push_to_stack(cpu, cpu->ip);
                cpu->ip += offset;
                break;
            }
            case OP_OUT_DXAL: {
                uint16_t port = emu_cpu_read_word_reg(cpu, REG_DX);
                uint16_t data = emu_cpu_read_byte_reg(cpu, REG_AL);
                cpu->ports[port](port, &data, 1, false);
                break;
            }
            case OP_OUT_DXAX: {
                uint16_t port = emu_cpu_read_word_reg(cpu, REG_DX);
                uint16_t data = emu_cpu_read_word_reg(cpu, REG_AX);
                cpu->ports[port](port, &data, 2, false);
                break;
            }
            case OP_IN_ALDX: {
                uint16_t port = emu_cpu_read_word_reg(cpu, REG_DX);
                uint16_t data = 0;
                cpu->ports[port](port, &data, 1, true);
                emu_cpu_write_byte_reg(cpu, REG_AL, data);
                break;
            }
            case OP_IN_AXDX: {
                uint16_t port = emu_cpu_read_word_reg(cpu, REG_DX);
                uint16_t data = 0;
                cpu->ports[port](port, &data, 2, true);
                emu_cpu_write_word_reg(cpu, REG_AX, data);
                break;
            }
            case OP_CLC: {
                emu_cpu_clear_flag(cpu, CF);
                break;
            }
            case OP_STC: {
                emu_cpu_mask_flag(cpu, CF);
                break;
            }
            case OP_CMC: {
                if (emu_cpu_read_flag(cpu, CF)) emu_cpu_clear_flag(cpu, CF);
                else emu_cpu_mask_flag(cpu, CF);
                break;
            }
            case OP_CLD: {
                emu_cpu_clear_flag(cpu, DF);
                break;
            }
            case OP_STD: {
                emu_cpu_mask_flag(cpu, DF);
                break;
            }
            case OP_CLI: {
                emu_cpu_clear_flag(cpu, IF);
                break;
            }
            case OP_STI: {
                emu_cpu_mask_flag(cpu, IF);
                break;
            }
            case OP_INCDEC_BT_REG: {
                uint8_t inwhich = NEXT_BYTE();
                if (inwhich >= 0xc8) {
                    uint8_t inwhichreg = inwhich - 0xc8;
                    emu_cpu_affect_flags_byte(cpu, emu_cpu_read_byte_reg(cpu, inwhichreg), -, 1);
                    emu_cpu_write_byte_reg(cpu, inwhichreg, emu_cpu_read_byte_reg(cpu, inwhichreg) - 1);
                } else if (inwhich >= 0xc0) {
                    uint8_t inwhichreg = inwhich - 0xc0;
                    emu_cpu_affect_flags_byte(cpu, emu_cpu_read_byte_reg(cpu, inwhichreg), +, 1);
                    emu_cpu_write_byte_reg(cpu, inwhichreg, emu_cpu_read_byte_reg(cpu, inwhichreg) + 1);
                } else {
                    printf("Unknown operator for operation INCDEC_BT_REG %d", inwhich);
                    return;
                }
                break;
            }
            default: {
                printf("Unknown opcode 0x%x\n", instruction);
                emu_cpu_do_interrupt(cpu, 6);
                return;
            }
        }
    }
#undef NEXT_SHORT
#undef NEXT_BYTE
}